#include "glare.d3d11.Driver.h"
#include "glare.d3d11.GpuBuffer.h"
#include "glare.d3d11.GpuResourceFactory.h"
#include "glare.d3d11.DrawContext.h"
#include "glare.d3d11.RenderWindow.h"
#include "glare.d3d11.ShaderFactory.h"
#include "glare.core.DriverCapabilities.h"
#include "glare.d3d11.DrawStateFactory.h"

namespace Glare {

	string D3D11Driver::Name = "D3D11";

	D3D11Driver::D3D11Driver()
		: PrimaryGpu(NULL)
		, SecondaryGpu(NULL)
		, TertiaryGpu(NULL)
		, QuaternaryGpu(NULL)

		, PrimaryMonitor(NULL)
		, SecondaryMonitor(NULL)
		, TertiaryMonitor(NULL)
		, QuaternaryMonitor(NULL)

		, _gpu_resources(NULL)
		, _gpu_shaders(NULL)
		, _draw_states(NULL)

		, _dxgi_factory(NULL)
	{
		_driver_caps.SetSupportedPrimitivesAll();

		// create a temporary DXGI factory, just for enumerating the available adapters & outputs
		IDXGIFactory* temp_dxgi_factory = NULL;
		GLARE_THROW(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)(&temp_dxgi_factory)), "Error while creating temporary DXGI factory");

		uint i = 0; 
		IDXGIAdapter* adapter; 
		std::vector<IDXGIAdapter*> adapters; 
		while(temp_dxgi_factory->EnumAdapters(i++, &adapter) != DXGI_ERROR_NOT_FOUND) 
			adapters.push_back(adapter); 

		std::for_each(adapters.begin(), adapters.end(), [&](IDXGIAdapter* adapter)
		{
			if (PrimaryGpu == NULL) PrimaryGpu = adapter;
			else if (SecondaryGpu == NULL) SecondaryGpu = adapter;
			else if (TertiaryGpu == NULL) TertiaryGpu = adapter;
			else if (QuaternaryGpu == NULL) QuaternaryGpu = adapter;

			uint i = 0;
			IDXGIOutput* output;
			std::vector<IDXGIOutput*> outputs;
			while(PrimaryGpu->EnumOutputs(i++, &output) != DXGI_ERROR_NOT_FOUND)
				outputs.push_back(output);

			std::for_each(outputs.begin(), outputs.end(), [&](IDXGIOutput* output)
			{
				if (PrimaryMonitor == NULL) PrimaryMonitor = output;
				else if (SecondaryMonitor == NULL) SecondaryMonitor = output;
				else if (TertiaryMonitor == NULL) TertiaryMonitor = output;
				else if (QuaternaryMonitor == NULL) QuaternaryMonitor = output;
			});
		});

		// release the temporary DXGI factory again
		GLARE_SAFE_RELEASE(temp_dxgi_factory, "Error while releasing temporary DXGI factory");

		D3D11_CREATE_DEVICE_FLAG flags = (D3D11_CREATE_DEVICE_FLAG)0;

#if _DEBUG
		flags = (D3D11_CREATE_DEVICE_FLAG)(flags | D3D11_CREATE_DEVICE_FLAG::D3D11_CREATE_DEVICE_DEBUG);
#endif

		D3D_FEATURE_LEVEL level;
		GLARE_THROW(D3D11CreateDevice(
			NULL, 
			D3D_DRIVER_TYPE::D3D_DRIVER_TYPE_HARDWARE, 
			NULL,
			flags,
			NULL, // use array here to use DX11.1
			0,
			D3D11_SDK_VERSION,
			&_d3d_device,
			&level,
			&_d3d_immediate_context), "Error while creating D3D device");

		GLARE_THROW(_d3d_device->QueryInterface(__uuidof(IDXGIDevice), (void**)&_dxgi_device), "Error while querying IDXGIDevice");
		GLARE_THROW(_dxgi_device->GetParent(__uuidof(IDXGIAdapter), (void**)&_dxgi_adapter), "Error while querying IDXGIAdapter");
		GLARE_THROW(_dxgi_adapter->GetParent(__uuidof(IDXGIFactory), (void **)&_dxgi_factory), "Error while querying IDXGIFactory");

		memset(&ThreadingSupport, 0, sizeof(D3D11_FEATURE_DATA_THREADING));
		GLARE_THROW(_d3d_device->CheckFeatureSupport(D3D11_FEATURE_THREADING, &ThreadingSupport, sizeof(ThreadingSupport)), "Error while checking D3D threading support");

		_gpu_resources = new D3D11GpuResourceFactory(this);
		_gpu_shaders = new D3D11ShaderFactory(this);
		_draw_states = new D3D11DrawStateFactory(this);

		_immediate_draw_context = new D3D11DrawContext(this, _d3d_immediate_context);
	}

	D3D11Driver::~D3D11Driver()
	{
		delete _draw_states;
		_draw_states = NULL;

		delete _gpu_shaders;
		_gpu_shaders = NULL;

		delete _gpu_resources;
		_gpu_resources = NULL;

		delete _immediate_draw_context;
		_immediate_draw_context = NULL;

		GLARE_SAFE_RELEASE(_d3d_device, "Error while releasing D3D device");
		GLARE_SAFE_RELEASE(_dxgi_device, "Error while releasing driver DXGI device");
		GLARE_SAFE_RELEASE(_dxgi_adapter, "Error while releasing driver DXGI adapter");
		GLARE_SAFE_RELEASE(_dxgi_factory, "Error while releasing driver DXGI factory");
	}

	std::string& D3D11Driver::GetName() const
	{
		return Name;
	}

	GpuResourceFactory* D3D11Driver::GetGpuResourceFactory() const
	{
		return _gpu_resources;
	}

	ShaderFactory* D3D11Driver::GetShaderFactory() const
	{
		return _gpu_shaders;
	}

	DrawStateFactory* D3D11Driver::GetDrawStateFactory() const
	{
		return _draw_states;
	}

	RenderWindow* D3D11Driver::CreateRenderWindow(PlatformWindow* platform_window)
	{
		auto render_window = new D3D11RenderWindow(this, platform_window);
		return render_window;
	}

	void D3D11Driver::DestroyRenderWindow(RenderWindow* render_window)
	{
		delete render_window;
	}

	const DriverStage::Type D3D11Driver::GetSupportedStages() const
	{
		auto supported_stages = (DriverStage::Type)(DriverStage::All & (~DriverStage::VertexTransform));
		return supported_stages;
	}

	const DriverCapabilities& D3D11Driver::GetCapabilities() const
	{
		return _driver_caps;
	}

	DrawContext* D3D11Driver::GetImmediateDrawContext() const
	{
		return _immediate_draw_context;
	}

	DrawContext* D3D11Driver::CreateDrawContext()
	{
		ID3D11DeviceContext* d3d_device_context;
		_d3d_device->CreateDeferredContext(0, &d3d_device_context);

		auto draw_context = new D3D11DrawContext(this, d3d_device_context);
		return draw_context;
	}

	void D3D11Driver::DestroyDrawContext(DrawContext* draw_context)
	{
		delete draw_context;
	}

	ID3D11Device* D3D11Driver::GetD3DDevice() const
	{
		return _d3d_device;
	}

	ID3D11DeviceContext* D3D11Driver::GetD3DImmediateContext() const
	{
		return _d3d_immediate_context;
	}

	IDXGIDevice* D3D11Driver::GetDXGIDevice() const
	{
		return _dxgi_device;
	}

	IDXGIAdapter* D3D11Driver::GetDXGIAdapter() const
	{
		return _dxgi_adapter;
	}

	IDXGIFactory* D3D11Driver::GetDXGIFactory() const
	{
		return _dxgi_factory;
	}
}
